#!/usr/local/bin/python3
# *_* coding: UTF-8 *_*
# @IDE: PyCharm
# @Version: Python3.97
# @Author: Kendrick.Kc
# @Email: 509556985@qq.com
# @File: set_dgs.py
# @Inst: 设置DGS数据
# @Time: 2022/8/30 21:50
# -----


import time
import traceback
import requests
import datetime
import math
import pandas
import pandas_ta
from copy import deepcopy
from utils.timestamp import now_time_stamp, next_time_stamp


class SetDGSGrid:
    def __init__(self, binance, symbol):
        self.binance = binance
        self.symbol = symbol
        self.rpcc_exchange_info()
        self.data_df = None

        # 初始数据的形成并写入
    def json_write(self, amount_multiple=1):
        res_tian_di = self.tian_and_di_order()  # 天地单
        res_tian_di_grid = self.set_grid(res_tian_di, amount_multiple)  # 设置天地单网格
        set_speed_tracking = self.set_speed_tracking(res_tian_di, amount_multiple)  # 设置极速追踪

        res_db = {
            "info": {
                "inst_id": self.symbol,
                "initial_balance": 0,  # 初始余额
                "balance": 0,  # 当前交易对的交易金额
                "isolated": self.binance.parameter.isolated,  # 是否为逐仓模式
                "leverage": 1,  # 杠杆
                "next": False,  # 是否准备进入下一轮/下一天
                "all_u": 0,  # 所有网格一共需要多少U
                "real_u": 0,  # 交易账户的可用U数
                "fear_greed_index": res_tian_di[2][0],  # 恐慌与贪婪指数
                "profit_spacing": res_tian_di[2][1],  # DGS利润间隔
                "grid_number": len(res_tian_di_grid[0]) + len(res_tian_di_grid[1]) + len(set_speed_tracking),  # 网格数量
                "start_number": now_time_stamp(),  # 开始交易的时间戳(作为往来信息的唯一标识)
                "next_time_stamp": next_time_stamp(self.binance, self.symbol)  # 下一天的时间戳(作为结束此币种交易)
            },
            "db": {
                "buy_kai_long": res_tian_di_grid[0],
                "sell_kai_short": res_tian_di_grid[1],
                "speed_tracking": set_speed_tracking
            }
        }
        res_db['info']['all_u'] = self.is_u_number(res_db)
        self.binance.cache_set(f"{self.binance.exchange_strategy}_{self.symbol}", res_db)
        return res_db

    # 获取Bar计算出天地单、网格数量
    def tian_and_di_order(self):
        self.data_df = self.candles(bar="30m", limit=50)
        fgi = self.fear_and_greed_index()   # 恐慌与贪婪指数 - 用于计算区间
        length = 15
        data = pandas_ta.bbands(self.data_df['close'], length, fgi[0])
        tian_dan = data[f"BBU_{length}_{fgi[0]}.0"].values[-1]
        di_dan = data[f"BBL_{length}_{fgi[0]}.0"].values[-1]
        # print(f"交易对：{self.symbol}, 天：{tian_dan}, 地：{di_dan}, 相差：{tian_dan - di_dan}")
        # exit()
        return tian_dan, di_dan, fgi

    # 设置网格
    def set_grid(self, data, amount_multiple):
        """ 等差网格 """

        tian_dan, di_dan, fgi = data[0], data[1], data[2][1]
        buy_kai_long_grid = []
        distance = tian_dan

        def next_grid(d_tian, d_profits):
            merge_cost = d_tian - (d_tian * d_profits)  # 计算下一格
            next_price = merge_cost - (merge_cost * (self.binance.parameter.commission * 2))  # 合并手续费
            return next_price

        def cover_grid(num):
            if num == 1:
                return tian_dan
            else:
                return buy_kai_long_grid[-1]["price"]

        for i in range(1000):
            profits = fgi * (1 + ((i + 1) / 10))  # 利润空间

            # 第一格dgs跳过
            if i == 0:
                distance = next_grid(distance, profits)
                continue

            # 差不多到达地单价格时不再计算
            if distance < (di_dan + (di_dan * profits)):
                break

            res_price = next_grid(distance, profits)  # 下一级的价格
            min_qty = round((self.binance.parameter.this_min_notional / res_price) + self.binance.parameter.this_min_qty, self.binance.parameter.this_decimal) * amount_multiple
            buy_kai_long_grid.append({
                "number": i - 1,
                "beginPrice": tian_dan,
                "endPrice": di_dan,
                "price": round(distance, self.binance.parameter.this_price_decimal),  # 该节点的价格
                "amount": min_qty,  # 订单数量
                "state": "idle",  # pending / idle / deal           # 节点状态
                "coverPrice": float(round(cover_grid(i), self.binance.parameter.this_price_decimal)),  # 节点平仓价格
                "orderId": "",  # 节点当前相关的订单的ID
                "posU": round((res_price * min_qty), self.binance.parameter.this_price_decimal)  # 需要多少U才能开仓
            })
            distance = res_price

        buy_kai_long_grid = buy_kai_long_grid[1:]  # 这里会去掉第一行，故此91行减1

        # 反向DGS
        sell_kai_short_grid = deepcopy(buy_kai_long_grid)
        for i in range(len(buy_kai_long_grid)):
            if i is len(buy_kai_long_grid) - 1:
                di_price = sell_kai_short_grid[i - 1]["price"] - sell_kai_short_grid[i]["price"]
                sell_kai_short_grid[i]["coverPrice"] = round(sell_kai_short_grid[i]["price"] - di_price,
                                                             self.binance.parameter.this_price_decimal)
                continue
            sell_kai_short_grid[i]["coverPrice"] = sell_kai_short_grid[i + 1]["price"]

        return buy_kai_long_grid, sell_kai_short_grid

    # 设置极速追踪
    def set_speed_tracking(self, data, amount_multiple, fgi_multiple=3):
        tian_dan, di_dan, fgi = data[0], data[1], data[2][1] * fgi_multiple    # fgi是移动止损百分比间距

        def speed_tracking(pos, buy_price, side, cover_price=0, pos_number=5):
            profits = buy_price * fgi
            min_qty = round((self.binance.parameter.this_min_notional / buy_price) + self.binance.parameter.this_min_qty, self.binance.parameter.this_decimal) * amount_multiple
            min_qty *= pos_number   # 仓位数量

            if side == 'BUY':
                cover_price = buy_price + profits
            elif side == 'SELL':
                cover_price = buy_price - profits

            tracking = {
                "open": False,
                "pos": pos,
                "buyPrice": buy_price,  # 开单价格
                "distance": round(profits, self.binance.parameter.this_price_decimal),  # 价格差的距离
                "coverPrice": round(cover_price, self.binance.parameter.this_price_decimal),
                "amount": min_qty,  # 订单数量
                "state": "idle",  # pending / idle / deal           # 节点状态
                "orderId": "",  # 节点当前相关的订单的ID
                "fgi": fgi,     # 移动止损百分比间距
                "posU": round((buy_price * min_qty), self.binance.parameter.this_price_decimal)  # 需要多少U才能开仓
            }
            return tracking

        return speed_tracking("s_t_buy_kai_long", tian_dan, "SELL"), speed_tracking("s_t_sell_kai_short", di_dan, "BUY")

    # 获取交易规则和交易对
    def rpcc_exchange_info(self):
        ex_res = self.binance.rest("while_info", "exchange_info")
        for item in ex_res["symbols"]:
            if item["symbol"] == self.symbol:
                for fil in item["filters"]:
                    self.binance.parameter.this_decimal = int(item["quantityPrecision"])  # 下单数量小数点位数
                    self.binance.parameter.this_price_decimal = int(item["pricePrecision"])  # 价格小数点位数

                    if fil["filterType"] == "LOT_SIZE":  # 数量限制
                        self.binance.parameter.this_min_qty = float(fil["minQty"])  # 订单最小数量限制

                    if fil["filterType"] == "MIN_NOTIONAL":  # 最小名义价值
                        self.binance.parameter.this_min_notional = float(fil["notional"])  # 最小下单金额
                break

    # 大盘BTC的恐慌与贪婪指数
    def fear_and_greed_index(self, reconnection=10, url="https://api.alternative.me/fng/?date_format=cn", number=5):
        """
        恐慌与贪婪指数
            加密货币贪婪恐慌指数的测量范围是 0-100，分数越低代表越恐惧，分数越高代表越贪婪。 0-24 分代表“极度恐惧”，
            意味着投资者正在大量抛售并退出市场。 25-49 分代表“恐惧”，这意味着相当数量的投资者正在抛售，而市场兴趣仍
            然不大。50-74 分代表“贪婪”，这意味着大量购买正在发生，导致价格上涨。 75-100 分代表“极度贪婪”，表明市场
            非常火爆，市场“泡沫”可能很快就会破裂。
        :param reconnection: 重连次数
        :param url: 恐慌与贪婪指数API
        :param number: 利润的求和长度
        :return: 恐慌与贪婪指数, DGS利润(间距)
        """

        def res_fg_list(natr):
            rfg_list = [(item / 10000) * natr for item in range(100, -1, -1)]

            for rfi in range(0, 101):
                if rfi < 25 or rfi > 75:
                    rfg_list[rfi] = (100 / 10000) * natr
                else:
                    if rfi < 50:
                        pass
                    elif rfi > 50:
                        rfg_list[rfi] = (rfi / 10000) * natr
                    else:
                        rfg_list[rfi] = (rfi / 10000) * natr
            return rfg_list

        self.data_df["n_atr"] = pandas_ta.natr(self.data_df['high'], self.data_df['low'], self.data_df['close'],
                                               timeperiod=len(self.data_df) - number)

        fg_list = res_fg_list(1 + (abs(sum(self.data_df["n_atr"].values[-number:])) / 100))

        for i in range(1, reconnection + 1):
            time.sleep(1)
            try:
                res = requests.get(url=url)
                if res.status_code == 200:
                    data = res.json()["data"][0]
                    if data["timestamp"] == datetime.datetime.now().strftime("%Y-%m-%d"):
                        self.binance.cache_set(self.binance.parameter.db_coll_fgi, data)
                        return int(data["value"]), fg_list[int(data["value"])]
            except:
                self.binance.logger_info(f"恐慌与贪婪指数请求失败{i}次, 报错:\n{traceback.format_exc()}")
                continue

        value = 50
        self.binance.logger_info(f"恐慌与贪婪指数请求失败. 返回默认间距：{fg_list[value]}")
        return value, fg_list[value]

    def dgs_leverage_brackets(self):
        """ 获取最大杠杆 """
        ex_res = self.binance.rest('while_info', 'leverage_brackets', symbol=self.symbol)
        return int(ex_res[0]['brackets'][0]['initialLeverage'])

    # 格式化数据为pandas
    def candles(self, bar='1d', limit=100):
        """ 获取数据并转换为pandas类型 """
        ex_res = self.binance.rest("while_info", "klines", symbol=self.symbol, interval=bar, limit=limit)
        data_df = pandas.DataFrame(ex_res)
        data_df.columns = ['openTime', 'open', 'high', 'low', 'close', 'vol', 'closeTime', 'Na', 'Na', 'Na', 'Na', 'Na']
        data_df = data_df.astype('float')
        data_df.dropna(inplace=True)
        return data_df

    @staticmethod
    def is_u_number(grid, add_margin=True):
        """
        返回DGS所需的U数
        :param grid: DGS
        :param add_margin: 是否加20%作为保证金（默认为是）
        :return:
        """
        amount_u = 0
        for item in grid['db']['buy_kai_long']:
            amount_u += item['posU']
        for item in grid['db']['sell_kai_short']:
            amount_u += item['posU']
        for item in grid['db']['speed_tracking']:
            amount_u += item['posU']
        return math.ceil(amount_u * 1.2 if add_margin else amount_u)  # 加20%作为保证金
